home *** CD-ROM | disk | FTP | other *** search
- /* @(#)src/smtprecv.c 1.16 9/6/92 01:33:48 */
-
- /*
- * Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
- * Copyright (C) 1992 Ronald S. Karr
- *
- * See the file COPYING, distributed with smail, for restriction
- * and warranty information.
- */
-
- /*
- * smtprecv.c:
- * Receive mail using the SMTP protocol.
- */
- #include <stdio.h>
- #include <ctype.h>
- #include <signal.h>
- #include "defs.h"
- #include "main.h"
- #include "smail.h"
- #include "addr.h"
- #include "dys.h"
- #include "log.h"
- #include "hash.h"
- #ifndef DEPEND
- # include "extern.h"
- # include "debug.h"
- # include "exitcodes.h"
- #endif
-
- /* library functions */
- extern long time();
-
- /* types local to this file */
- enum e_smtp_commands {
- HELO_CMD, /* HELO domain */
- MAIL_CMD, /* MAIL FROM:<sender> */
- RCPT_CMD, /* RCPT TO:<recipient> */
- DATA_CMD, /* DATA */
- VRFY_CMD, /* VRFY */
- EXPN_CMD, /* EXPN */
- QUIT_CMD, /* QUIT */
- RSET_CMD, /* RSET */
- NOOP_CMD, /* NOOP */
- DEBUG_CMD, /* DEBUG [level] */
- HELP_CMD, /* HELP */
- EOF_CMD, /* end of file encountered */
- OTHER_CMD, /* unknown command */
- };
-
- /* functions local to this file */
- static void reset_state();
- static enum e_smtp_commands read_smtp_command();
- static void expand_addr();
- static void verify_addr();
- static void smtp_input_signals();
- static void smtp_processing_signals();
- static void set_term_signal();
- static void smtp_receive_timeout_sig();
- static void smtp_sig_unlink();
-
- /* variables local to this file */
- static char *data; /* interesting data within input */
-
- static int term_signal;
- static int smtp_remove_on_timeout;
- static FILE *out_file;
- static char *help_msg[] = {
- "250-The following SMTP commands are recognized:",
- "250-",
- "250- HELO hostname - startup and give your hostname",
- "250- MAIL FROM:<sender-address> - start transaction from sender",
- "250- RCPT TO:<recipient-address> - name recipient for message",
- "250- VRFY <address> - verify deliverability of address",
- "250- EXPN <address> - expand mailing list address",
- "250- DATA - start text of mail message",
- "250- RSET - reset state, drop transaction",
- "250- NOOP - do nothing",
- "250- DEBUG [level] - set debugging level, default 1",
- "250- HELP - produce this help message",
- "250- QUIT - close SMTP connection",
- "250-",
- "250-The normal sequence of events in sending a message is to state the",
- "250-sender address with a MAIL FROM command, give the recipients with",
- "250-as many RCPT TO commands as are required (one address per command)",
- "250-and then to specify the mail message text after the DATA command.",
- "250 Multiple messages may be specified. End the last one with a QUIT."
- };
-
-
-
- /*
- * receive_smtp - receive mail over SMTP.
- *
- * Take SMTP commands on the `in' file. Send reply messages
- * to the `out' file. If `out' is NULL, then don't send reply
- * messages (i.e., read batch SMTP commands).
- *
- * return an array of spool files which were created in this SMTP
- * conversation.
- *
- * The last spooled message is left open as an efficiency move, so the
- * caller must arrange to close it or process it to completion. As
- * well, it is the callers responsibility to close the input and
- * output channels.
- */
- char **
- receive_smtp(in, out)
- FILE *in; /* stream of SMTP commands */
- FILE *out; /* channel for responses */
- {
- char *error; /* temp to hold error messages */
- struct addr *cur;
- static char **files = NULL;
- static int file_cnt = 7; /* initially put 7 parts in array */
- int file_i = 0; /* index starts at the beginning */
- /* save important state to restore after initialize_state() */
- enum er_proc save_error_proc = error_processing;
- int save_do_aliasing = do_aliasing;
- int save_dont_deliver = dont_deliver;
- FILE *save_errfile = errfile;
- int save_debug = debug;
- int temp, i;
-
- /* initialize state */
- initialize_state();
-
- /* restore important state */
- error_processing = save_error_proc;
- do_aliasing = save_do_aliasing;
- dont_deliver = save_dont_deliver;
-
- term_signal = FALSE;
- out_file = out;
- smtp_processing_signals();
- if (out) {
- (void) signal(SIGALRM, smtp_receive_timeout_sig);
- }
-
- /* allocate an initial chunk of spool filename slots */
- if (files == NULL) {
- files = (char **)xmalloc((file_cnt + 1) * sizeof(*files));
- }
-
- /* output the startup banner line */
- if (out) {
- char *s;
-
- s = expand_string(smtp_banner, (struct addr *)NULL,
- (char *)NULL, (char *)NULL);
- while (*s) {
- fprintf(out, "220%c", index(s, '\n') == NULL? ' ': '-');
- while (*s) {
- putc(*s, out);
- if (*s++ == '\n') break;
- }
- }
- putc('\r', out);
- putc('\n', out);
- fflush(out);
- }
-
- while (! term_signal || out == NULL) {
- if (out) {
- alarm(smtp_receive_command_timeout);
- }
- switch (read_smtp_command(in)) {
- case HELO_CMD:
- strip_rfc822_comments(data);
- if (out && data[0] == '\0') {
- fprintf(out, "501 HELO requires domain name as operand\r\n");
- fflush(out);
- break;
- }
- if (sender_host == NULL && data[0] != '\0') {
- sender_host = COPY_STRING(data);
- }
- if (sender_proto == NULL) {
- sender_proto = (out? "smtp": "bsmtp");
- }
- if (out) {
- fprintf(out, "250 %s Hello %s\r\n", primary_name, data);
- fflush(out);
- }
- reset_state();
- break;
-
- case MAIL_CMD:
- strip_rfc822_comments(data);
- if (out && data[0] == '\0') {
- fprintf(out, "501 MAIL FROM requires address as operand\r\n");
- fflush(out);
- break;
- }
- if (sender) {
- if (out) {
- fprintf(out, "503 Sender already specified\r\n");
- fflush(out);
- }
- break;
- }
- sender = preparse_address(data, &error);
- if (out) {
- if (sender) {
- fprintf(out, "250 <%s> ... Sender Okay\r\n",
- sender);
- } else {
- fprintf(out, "501 <%s> ... %s\r\n", data, error);
- }
- fflush(out);
- }
- if (sender && sender[0] == '\0') {
- /* special error sender form <> given */
- sender = COPY_STRING("<>");
- }
- if (sender && EQ(sender, "+")) {
- /* special smail-internal <+> was given */
- sender = COPY_STRING("<+>");
- }
- break;
-
- case RCPT_CMD:
- strip_rfc822_comments(data);
- if (out && data[0] == '\0') {
- fprintf(out, "501 RCPT TO requires address as operand\r\n");
- fflush(out);
- break;
- }
- cur = alloc_addr();
- if (out) {
- if (cur->work_addr = preparse_address(data, &error)) {
- fprintf(out, "250 <%s> ... Recipient Okay\r\n",
- cur->work_addr);
- fflush(out);
- } else {
- fprintf(out, "501 <%s> ... %s\r\n", data, error);
- fflush(out);
- break;
- }
- }
- /*
- * surround in angle brackets, if the addr begins with `-'.
- * This will avoid ambiguities in the data dumped to the spool
- * file.
- */
- if (data[0] == '-') {
- cur->in_addr = xprintf("<%s>", data);
- } else {
- cur->in_addr = COPY_STRING(data);
- }
- cur->succ = recipients;
- recipients = cur;
- break;
-
- case DATA_CMD:
- if (sender == NULL) {
- if (out) {
- fprintf(out, "503 Need MAIL command\r\n");
- fflush(out);
- } else {
- /* sink the message for the sake of further batched cmds */
- if (spool_fn) {
- close_spool();
- }
- swallow_smtp(in);
- }
- break;
- }
- if (recipients == NULL) {
- if (out) {
- fprintf(out, "503 Need RCPT (recpient)\r\n");
- fflush(out);
- } else {
- /* sink the message for the sake of further batched cmds */
- if (spool_fn) {
- close_spool();
- }
- swallow_smtp(in);
- }
- break;
- }
- if (out) {
- fprintf(out,
- "354 Enter mail, end with \".\" on a line by itself\r\n");
- fflush(out);
- alarm(0);
- }
-
- /*
- * if we had the previous spool file opened, close it
- * before creating a new one
- */
- if (spool_fn) {
- close_spool();
- }
- if (out) {
- /*
- * if we are not interactive and cannot send back failure
- * messages, always try to accept the complete message.
- */
- smtp_input_signals();
- alarm(smtp_receive_message_timeout);
- }
- smtp_remove_on_timeout = 1;
- if (queue_message(in, SMTP_DOTS, recipients, &error) == FAIL) {
- exitvalue = EX_IOERR;
- log_spool_errors();
- if (out) {
- fprintf(out, "451 Failed to queue message: %s: %s\r\n",
- error, strerrno());
- fflush(out);
- break;
- } else if (errfile) {
- fprintf(errfile, "Failed to queue message: %s: %s\r\n",
- error, strerrno());
- }
- }
- smtp_processing_signals();
- if (sender == NULL) {
- unlink_spool();
- reset_state();
- break;
- }
- if (read_message() == NULL) {
- log_spool_errors();
- unlink_spool();
- if (out) {
- fprintf(out, "451 error in spooled message\r\n");
- fflush(out);
- }
- break;
- }
- alarm(0);
- smtp_remove_on_timeout = 0;
-
- check_grade();
- log_incoming();
- log_spool_errors();
- if (out) {
- fprintf(out, "250 Mail accepted\r\n");
- fflush(out);
- }
- /* always allow an extra element to store the ending NULL */
- if (file_i >= file_cnt) {
- /* we need to grow the array of spool file names */
- file_cnt += 8;
- files = (char **)xrealloc((char *)files,
- (file_cnt + 1) * sizeof(*files));
- }
- files[file_i++] = xprintf("%s/input/%s", spool_dir, spool_fn);
- reset_state();
- break;
-
- case VRFY_CMD:
- if (out) {
- #ifdef NO_VERIFY
- fprintf(out, "502 Command not implemented\r\n");
- #else
- strip_rfc822_comments(data);
- verify_addr(data, out);
- fflush(out);
- #endif
- }
- break;
-
- case EXPN_CMD:
- if (out) {
- #ifdef NO_VERIFY
- fprintf(out, "502 Command not implemented\r\n");
- #else
- strip_rfc822_comments(data);
- expand_addr(data, out);
- fflush(out);
- #endif
- }
- break;
-
- case QUIT_CMD:
- if (out) {
- fprintf(out, "221 %s closing connection\r\n",
- primary_name);
- fflush(out);
- }
- reset_state();
- files[file_i++] = NULL;
- errfile = save_errfile;
- debug = save_debug;
- return files;
-
- case RSET_CMD:
- reset_state();
- if (out) {
- fprintf(out, "250 Reset state\r\n");
- fflush(out);
- }
- break;
-
- case NOOP_CMD:
- if (out) {
- fprintf(out, "250 Okay\r\n");
- fflush(out);
- }
- break;
-
- case DEBUG_CMD:
- if (out) {
- #ifndef NODEBUG
- if (smtp_debug) {
- strip_rfc822_comments(data);
- if (data[0]) {
- error = NULL;
- temp = c_atol(data, &error);
- if (error) {
- fprintf(out, "500 bad number: %s\r\n", error);
- fflush(out);
- break;
- }
- } else {
- temp = 1;
- }
- if (temp == 0) {
- fprintf(out, "250 Debugging disabled\r\n");
- } else {
- DEBUG(DBG_QUEUE_LO,
- "debugging output grabbed by SMTP\r\n");
- fprintf(out, "250 Debugging level: %d\r\n", temp);
- }
- fflush(out);
- debug = temp;
- errfile = out;
- break;
- }
- #endif /* NODEBUG */
- fprintf(out, "500 I hear you knocking, but you can't come in\r\n");
- fflush(out);
- }
- break;
-
- case HELP_CMD:
- if (out) {
- for (i = 0; i < TABLESIZE(help_msg); i++) {
- fprintf(out, "%s\r\n", help_msg[i]);
- }
- fflush(out);
- }
- break;
-
- case EOF_CMD:
- if (out) {
- fprintf(out, "421 %s Lost input channel\r\n", primary_name);
- fflush(out);
- }
- files[file_i++] = NULL;
- errfile = save_errfile;
- debug = save_debug;
- return files;
-
- default:
- if (out) {
- fprintf(out, "500 Command unrecognized\r\n");
- fflush(out);
- }
- break;
- }
- }
-
- /*
- * we appear to have received a SIGTERM, so shutdown and tell the
- * remote host.
- */
- fprintf(out, "421 %s Service not available, closing channel\r\n",
- primary_name);
- fflush(out);
-
- files[file_i] = NULL;
- errfile = save_errfile;
- debug = save_debug;
- return files;
- }
-
- static void
- reset_state()
- {
- struct addr *cur;
- struct addr *next;
-
- for (cur = recipients; cur; cur = next) {
- next = cur->succ;
- xfree(cur->in_addr);
- if (cur->work_addr) {
- /* work_addr is defined only for interactive smtp */
- xfree(cur->work_addr);
- }
- xfree((char *)cur);
- }
- recipients = NULL;
-
- if (sender) {
- xfree(sender);
- sender = NULL;
- }
- }
-
- static enum e_smtp_commands
- read_smtp_command(f)
- register FILE *f; /* SMTP command stream */
- {
- static struct str input; /* buffer storing recent command */
- static int inited = FALSE; /* TRUE if input initialized */
- register int c; /* input char */
- static struct smtp_cmd_list {
- char *name;
- int len;
- enum e_smtp_commands cmd;
- } smtp_cmd_list[] = {
- "HELO", sizeof("HELO")-1, HELO_CMD,
- "MAIL FROM:", sizeof("MAIL FROM:")-1, MAIL_CMD,
- "RCPT TO:", sizeof("RCPT TO:")-1, RCPT_CMD,
- "DATA", sizeof("DATA")-1, DATA_CMD,
- "VRFY", sizeof("VRFY")-1, VRFY_CMD,
- "EXPN", sizeof("EXPN")-1, EXPN_CMD,
- "QUIT", sizeof("QUIT")-1, QUIT_CMD,
- "RSET", sizeof("RSET")-1, RSET_CMD,
- "NOOP", sizeof("NOOP")-1, NOOP_CMD,
- "DEBUG", sizeof("DEBUG")-1, DEBUG_CMD,
- "HELP", sizeof("HELP")-1, HELP_CMD,
- };
- struct smtp_cmd_list *cp;
-
- if (! inited) {
- STR_INIT(&input);
- inited = TRUE;
- } else {
- input.i = 0;
- }
- while ((c = getc(f)) != '\n' && c != EOF) {
- STR_NEXT(&input, c);
- }
- if (input.p[input.i - 1] == '\r') {
- input.p[input.i - 1] = '\0';
- } else {
- STR_NEXT(&input, '\0');
- }
-
- /* return end of file pseudo command if end of file encountered */
- if (c == EOF) {
- return EOF_CMD;
- }
-
- for (cp = smtp_cmd_list; cp < ENDTABLE(smtp_cmd_list); cp++) {
- if (strncmpic(cp->name, input.p, cp->len) == 0) {
- for (data = input.p + cp->len; isspace(*data); data++) ;
- return cp->cmd;
- }
- }
-
- return OTHER_CMD;
- }
-
- #ifndef NO_VERIFY
- /*
- * expand_addr - expand an address
- *
- * display the list of items that an address expands to.
- */
- static void
- expand_addr(in_addr, out)
- char *in_addr; /* input address string */
- FILE *out; /* write expansion here */
- {
- struct addr *addr = alloc_addr(); /* get an addr structure */
- struct addr *okay = NULL; /* list of deliverable addrs */
- struct addr *defer = NULL; /* list of currently unknown addrs */
- struct addr *fail = NULL; /* list of undeliverable addrs */
- char *error; /* hold error message */
-
- addr->in_addr = in_addr; /* setup the input addr structure */
- /* build the mungeable addr string */
- addr->work_addr = preparse_address(in_addr, &error);
- if (addr->work_addr == NULL) {
- fprintf(out, "501 %s ... %s\r\n", in_addr, error);
- fflush(out);
- return;
- }
-
- /* cache directors and routers on the assumption we will need them again */
- if (! queue_only) {
- if (! cached_directors) {
- cache_directors();
- }
- if (! cached_routers) {
- cache_routers();
- }
- }
-
- hit_table = new_hash_table(hit_table_len,
- (struct block *)NULL,
- HASH_DEFAULT);
- resolve_addr_list(addr, &okay, &defer, &fail, TRUE);
- if (okay) {
- register struct addr *cur; /* current addr to display */
-
- /* display the complete list of resolved addresses */
- for (cur = okay; cur->succ; cur = cur->succ) {
- fprintf(out, "250-%s\r\n", cur->in_addr);
- fflush(out);
- }
- /* the last one should not begin with 250- */
- fprintf(out, "250 %s\r\n", cur->in_addr);
- } else {
- /* just say we couldn't find it */
- fprintf(out, "550 %s ... not matched\r\n", in_addr);
- }
- }
-
- /*
- * verify_addr - verify an address
- *
- * redisplay the input address if it is a valid address.
- */
- static void
- verify_addr(in_addr, out)
- char *in_addr; /* input address string */
- FILE *out; /* write expansion here */
- {
- struct addr *addr = alloc_addr(); /* get an addr structure */
- struct addr *okay = NULL; /* verified address */
- struct addr *defer = NULL; /* temporarily unverifiable addr */
- struct addr *fail = NULL; /* unverified addr */
- char *error; /* hold error message */
-
- addr->in_addr = in_addr; /* setup the input addr structure */
- /* build the mungeable addr string */
- addr->work_addr = preparse_address(in_addr, &error);
- if (addr->work_addr == NULL) {
- fprintf(out, "501 %s ... %s\r\n", in_addr, error);
- fflush(out);
- return;
- }
-
- /* cache directors and routers on the assumption we will need them again */
- if (! queue_only) {
- if (! cached_directors) {
- cache_directors();
- }
- if (! cached_routers) {
- cache_routers();
- }
- }
- verify_addr_list(addr, &okay, &defer, &fail);
-
- if (okay) {
- fprintf(out, "250 %s\r\n", in_addr);
- } else if (defer) {
- fprintf(out, "550 %s ... cannot verify: %s\r\n", in_addr,
- defer->error->message);
- } else if (fail) {
- fprintf(out, "550 %s ... not matched: %s\r\n", in_addr,
- fail->error->message);
- } else {
- /* hmmm, it should have been in one of the lists */
- fprintf(out, "550 %s ... not matched\r\n", in_addr);
- }
- }
- #endif /* NO_VERIFY */
-
-
- /*
- * smtp_input_signals - setup signals for reading in message with smtp
- *
- * Basically, unlink the message except in the case of SIGTERM, which
- * will cause sig_term and queue_only to be set.
- */
- static void
- smtp_input_signals()
- {
- if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
- (void) signal(SIGHUP, smtp_sig_unlink);
- }
- if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
- (void) signal(SIGINT, smtp_sig_unlink);
- }
- (void) signal(SIGTERM, set_term_signal);
- }
-
- /*
- * smtp_processing_signals - setup signals for getting smtp commands
- *
- * basically, everything interesting should cause a call to
- * set_term_signal.
- */
- static void
- smtp_processing_signals()
- {
- if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
- (void) signal(SIGHUP, set_term_signal);
- }
- if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
- (void) signal(SIGINT, set_term_signal);
- }
- (void) signal(SIGTERM, set_term_signal);
- }
-
- /*
- * set_term_signal - set term_signal and queue_only
- *
- * This is used by signals to abort SMTP command processing and to
- * prevent attempting delivery.
- *
- * NOTE: This doesn't work correctly for systems that lack restartable
- * system calls, as read will return EINTR for such systems,
- * rather than continuing. This situation could be improved,
- * though it doesn't really seem worth the rather large amount
- * of bother required.
- */
- static void
- set_term_signal(sig)
- int sig;
- {
- (void) signal(sig, set_term_signal);
- term_signal = TRUE;
- queue_only = TRUE;
- }
-
- /*
- * smtp_receive_timeout_sig - timeout SMTP
- */
-
- static void
- smtp_receive_timeout_sig(sig)
- int sig;
- {
- fprintf(out_file, "421 %s SMTP command timeout, closing channel\r\n",
- primary_name);
- write_log(LOG_SYS, "SMTP connection timeout%s%s.",
- sender_host? "while talking with": "",
- sender_host? sender_host: "");
- if (smtp_remove_on_timeout) {
- unlink_spool();
- }
- exit(EX_TEMPFAIL);
- }
-
- /*
- * smtp_sig_unlink - unlink spool file and fast exit.
- *
- * This is useful for handling signals to abort reading a message in
- * with SMTP.
- */
- static void
- smtp_sig_unlink(sig)
- int sig;
- {
- (void) signal(sig, SIG_IGN);
- if (out_file) {
- fprintf(out_file, "421 %s Service not available, closing channel\r\n",
- primary_name);
- }
- unlink_spool();
- exit(EX_OSFILE);
- }
-